/******************************************************************************
* Copyright (C) Ultraleap, Inc. 2011-2021. *
* *
* Use subject to the terms of the Apache License 2.0 available at *
* http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
* between Ultraleap and you, your company or other organization. *
******************************************************************************/
using Leap.Unity.Attributes;
#if UNITY_2017_2_OR_NEWER
using UnityEngine.XR;
#else
using UnityEngine.VR;
#endif
using System;
using System.Collections.Generic;
using UnityEngine;
using Leap.Unity.Query;
using Leap.Unity.Space;
using UnityEngine.Serialization;
namespace Leap.Unity.Interaction
{
[DisallowMultipleComponent]
public class InteractionXRController : InteractionController
{
#region Inspector
[Header("Controller Configuration")]
[Tooltip("Read-only. InteractionXRControllers use Unity's built-in XRNode tracking "
+ "API to receive tracking data for XR controllers by default. If you add a "
+ "custom script to set this controller's trackingProvider to something "
+ "other than the DefaultXRNodeTrackingProvider, the change will be reflected "
+ "here. (Hint: Use the ExecuteInEditMode attribute.)")]
[Disable, SerializeField]
#pragma warning disable 0414
private string _trackingProviderType = "DefaultXRNodeTrackingProvider";
#pragma warning restore 0414
public bool isUsingCustomTracking
{
get { return !(trackingProvider is DefaultXRNodeTrackingProvider); }
}
[Tooltip("If this string is not empty and does not match a controller in "
+ "Input.GetJoystickNames(), then this game object will disable itself.")]
[SerializeField, EditTimeOnly]
[FormerlySerializedAs("_deviceString")]
private string _deviceJoystickTokens = "oculus touch right"; // or, e.g., "openvr controller right"
public string deviceJoystickTokens { get { return _deviceJoystickTokens; } }
#pragma warning disable 0649
[Tooltip("Which hand will hold this controller? This property cannot be changed "
+ "at runtime.")]
[SerializeField, EditTimeOnly]
private Chirality _chirality;
public Chirality chirality { get { return _chirality; } }
#pragma warning restore 0649
[Tooltip("Whether to continuously poll attached joystick data for a joystick that "
+ "matches the device joystick tokens, using Input.GetJoystickNames(). This "
+ "call allocates garbage, so be wary of setting a low polling interval.")]
[SerializeField, OnEditorChange("pollConnection")]
private bool _pollConnection = true;
///
/// Whether to continuously poll attached joystick data for a joystick that
/// matches the device joystick tokens, using Input.GetJoystickNames(). This
/// call allocates garbage, so be wary of setting a low polling interval.
///
/// The connection is polled only until a joystick is detected to minimize allocation.
/// Once a joystick has been detected (isJoystickDetected), you must manually call
/// RefreshControllerConnection() to check if the joystick is no longer detected.
///
public bool pollConnection
{
get { return _pollConnection; }
set { _pollConnection = value; }
}
[Tooltip("The period in seconds at which to check if the controller is connected "
+ "by calling Input.GetJoystickNames(). This call allocates garbage, so be "
+ "wary of setting a low polling interval.")]
[MinValue(0f)]
[DisableIf("_pollConnection", isEqualTo: false)]
public float pollConnectionInterval = 2f;
[Header("Hover Configuration")]
[Tooltip("This is the point used to determine the distance to objects for the "
+ "purposes of their 'hovered' state. Generally, it should be somewhere "
+ "between the tip of the controller and the controller's center of mass.")]
[SerializeField]
private Transform _hoverPoint;
[Tooltip("These points refine the hover point when determining distances to "
+ "interaction objects for evaluating which object should be the primary hover "
+ "of this interaction controller. An object's proximity to one of these "
+ "points is interpreted as the user's intention to interact specifically "
+ "with that object, and is important when building less accident-prone user "
+ "interfaces. For example, hands place their primary hover points on the "
+ "thumb, index finger, and middle finger by default. Controllers generally "
+ "should have a primary hover point at any tip of the controller you expect "
+ "users might use to hit a button. Warning: Each point costs distance checks "
+ "against nearby objects, so making this list large is costly!")]
[SerializeField]
public new List primaryHoverPoints;
[Header("Grasping Configuration")]
[Tooltip("The point around which to check objects eligible for being grasped. Only "
+ "objects with an InteractionBehaviour component with ignoreGrasping disabled "
+ "are eligible for grasping. Upon attempting to grasp with a controller, the "
+ "object closest to the grasp point is chosen for grasping.")]
public Transform graspPoint;
public float maxGraspDistance = 0.06F;
[Tooltip("This string should match an Axis specified in Edit->Project Settings->"
+ "Input. This is the button to use to listen for grasping.")]
public string graspButtonAxis;
[Tooltip("The duration of time in seconds beyond initially pressing the grasp button "
+ "that the user can move the grasp point within range of a graspable "
+ "interaction object and still trigger a grasp. With a value of zero, objects "
+ "can only be grasped if they are already within the grasp distance of the "
+ "grasp point.")]
public float graspTimingSlop = 0.10F;
[Header("Enable/Disable GameObjects with Tracking State")]
[Tooltip("These objects will be made active only while the controller is tracked. "
+ "For more fine-tuned behavior, we recommend implementing your own logic. "
+ "controller.isJoystickDetected and controller.isTracked are useful for this.")]
[SerializeField]
private List _enableObjectsOnlyWhenTracked;
///
/// These objects will be made active only while the controller is tracked. For more
/// fine-tuned behavior, we recommend implementing your own logic.
///
/// controller.isJoystickDetected and controller.isTracked are useful for this.
///
public List enableObjectsOnlyWhenTracked
{
get
{
if (_enableObjectsOnlyWhenTracked == null)
{
_enableObjectsOnlyWhenTracked = new List();
}
return _enableObjectsOnlyWhenTracked;
}
}
#endregion // Inspector
#region Unity Events
protected override void Reset()
{
base.Reset();
hoverEnabled = true;
contactEnabled = true;
graspingEnabled = true;
trackingProvider = _defaultTrackingProvider;
_hoverPoint = null;
primaryHoverPoints.Clear();
graspPoint = null;
maxGraspDistance = 0.06F;
graspTimingSlop = 0.1F;
}
protected virtual void OnValidate()
{
_trackingProviderType = trackingProvider.GetType().ToString();
}
protected override void Start()
{
base.Start();
trackingProvider.OnTrackingDataUpdate += refreshControllerTrackingData;
}
protected override void fixedUpdateController()
{
fixedUpdatePollConnection();
if (isTracked)
{
foreach (var gameObject in enableObjectsOnlyWhenTracked)
{
gameObject.SetActive(true);
}
}
else
{
foreach (var gameObject in enableObjectsOnlyWhenTracked)
{
gameObject.SetActive(false);
}
}
refreshContactBoneTargets();
}
#endregion
#region Controller Connection Polling
private bool _isJoystickDetected = false;
///
/// Whether the device joystick tokens matched an entry in Input.GetJoystickNames().
/// If pollConnection is set to true, this status is refreshed periodically based on
/// the pollConnectionInterval, but only while the joystick tokens have not been
/// detected from Input.GetJoystickNames(). Call RefreshControllerConnection() to
/// detect if the controller has been disconnected.
///
/// Joystick detection is skipped if deviceJoystickTokens is null or empty, causing
/// this check to always return true.
///
public bool isJoystickDetected
{
get { return string.IsNullOrEmpty(deviceJoystickTokens) || _isJoystickDetected; }
}
private float _pollTimer = 0f;
private void fixedUpdatePollConnection()
{
if (_pollConnection && !_isJoystickDetected)
{
_pollTimer += Time.fixedDeltaTime;
}
if (_pollConnection && _pollTimer >= pollConnectionInterval)
{
_pollTimer = 0f;
RefreshControllerConnection();
}
}
public void RefreshControllerConnection()
{
_isJoystickDetected = true; // deviceJoystickTokens must be parsible to falsify.
if (deviceJoystickTokens != null && deviceJoystickTokens.Length > 0)
{
string[] joysticksConnected = Input.GetJoystickNames().Query()
.Select(s => s.ToLower()).ToArray();
string[] controllerSupportTokens = deviceJoystickTokens.ToLower()
.Split(" ".ToCharArray());
bool matchesController = joysticksConnected.Query()
.Any(joystick => controllerSupportTokens.Query()
.All(token => joystick.Contains(token)));
_isJoystickDetected = matchesController;
}
}
#endregion
#region Controller Tracking
private bool _hasTrackedPositionLastFrame = false;
private Vector3 _trackedPositionLastFrame = Vector3.zero;
private Quaternion _trackedRotationLastFrame = Quaternion.identity;
private IXRControllerTrackingProvider _backingTrackingProvider = null;
public IXRControllerTrackingProvider trackingProvider
{
get
{
if (_backingDefaultTrackingProvider == null)
{
_backingDefaultTrackingProvider = _defaultTrackingProvider;
}
return _backingDefaultTrackingProvider;
}
set
{
if (_backingTrackingProvider != null)
{
_backingTrackingProvider.OnTrackingDataUpdate -= refreshControllerTrackingData;
}
_backingTrackingProvider = value;
if (_backingTrackingProvider != null)
{
_backingTrackingProvider.OnTrackingDataUpdate += refreshControllerTrackingData;
}
}
}
private IXRControllerTrackingProvider _backingDefaultTrackingProvider;
private IXRControllerTrackingProvider _defaultTrackingProvider
{
get
{
if (_backingDefaultTrackingProvider == null)
{
refreshDefaultTrackingProvider();
}
return _backingDefaultTrackingProvider;
}
set
{
_backingDefaultTrackingProvider = value;
}
}
private void refreshDefaultTrackingProvider()
{
var defaultProvider = gameObject.GetComponent();
if (defaultProvider == null)
{
defaultProvider = gameObject.AddComponent();
}
defaultProvider.xrNode = this.xrNode;
_defaultTrackingProvider = defaultProvider;
}
private void refreshControllerTrackingData(Vector3 position, Quaternion rotation)
{
refreshIsBeingMoved(position, rotation);
if (_hasTrackedPositionLastFrame)
{
_trackedPositionLastFrame = this.transform.position;
_trackedRotationLastFrame = this.transform.rotation;
}
this.transform.position = position;
this.transform.rotation = rotation;
refreshContactBoneTargets();
if (!_hasTrackedPositionLastFrame)
{
_hasTrackedPositionLastFrame = true;
_trackedPositionLastFrame = this.transform.position;
_trackedRotationLastFrame = this.transform.rotation;
}
}
#endregion
#region Movement Detection
private const float RIG_LOCAL_MOVEMENT_SPEED_THRESHOLD = 00.07F;
private const float RIG_LOCAL_MOVEMENT_SPEED_THRESHOLD_SQR
= RIG_LOCAL_MOVEMENT_SPEED_THRESHOLD * RIG_LOCAL_MOVEMENT_SPEED_THRESHOLD;
private const float RIG_LOCAL_ROTATION_SPEED_THRESHOLD = 10.00F;
private const float BEING_MOVED_TIMEOUT = 0.5F;
private float _lastTimeMoved = 0F;
private bool _isBeingMoved = false;
private void refreshIsBeingMoved(Vector3 position, Quaternion rotation)
{
var isMoving = false;
var baseTransform = this.manager.transform;
// Check translation speed, relative to the Interaction Manager.
var baseLocalPos = baseTransform.InverseTransformPoint(position);
var baseLocalPosLastFrame = baseTransform.InverseTransformPoint(
_trackedPositionLastFrame);
var baseLocalSqrSpeed = ((baseLocalPos - baseLocalPosLastFrame)
/ Time.fixedDeltaTime).sqrMagnitude;
if (baseLocalSqrSpeed > RIG_LOCAL_MOVEMENT_SPEED_THRESHOLD_SQR)
{
isMoving = true;
}
// Check rotation speed, relative to the Interaction Manager.
var baseLocalRot = baseTransform.InverseTransformRotation(rotation);
var baseLocalRotLastFrame = baseTransform.InverseTransformRotation(
_trackedRotationLastFrame);
var baseLocalAngularSpeed = Quaternion.Angle(baseLocalRot, baseLocalRotLastFrame)
/ Time.fixedDeltaTime;
if (baseLocalAngularSpeed > RIG_LOCAL_ROTATION_SPEED_THRESHOLD)
{
isMoving = true;
}
if (isMoving)
{
_lastTimeMoved = Time.fixedTime;
}
// "isMoving" lasts for a bit after the controller stops moving, to avoid
// rapid oscillation of the value.
var timeSinceLastMoving = Time.fixedTime - _lastTimeMoved;
_isBeingMoved = trackingProvider != null && trackingProvider.isTracked
&& timeSinceLastMoving < BEING_MOVED_TIMEOUT;
}
#endregion
#region General InteractionController Implementation
///
/// Gets whether or not the underlying controller is currently tracked and any
/// joystick token filtering has confirmed that this controller has been detected as
/// a connected joystick.
///
public override bool isTracked
{
get
{
return isJoystickDetected && trackingProvider != null && trackingProvider.isTracked;
}
}
///
/// Gets whether or not the underlying controller is currently being moved in world
/// space, but relative to the Interaction Manager's transform. The Interaction
/// Manager is usually a sibling of the main camera beneath the camera rig transform,
/// so that if your application is only translating the player rig in space, this
/// method won't incorrectly return true.
///
public override bool isBeingMoved
{
get
{
return _isBeingMoved;
}
}
///
/// Gets the XRNode associated with this XR controller. Note: If the tracking mode
/// for this controller is specified as ControllerTrackingMode.Custom, this value
/// may be ignored.
///
///
#if UNITY_2017_2_OR_NEWER
public XRNode xrNode
{
get { return chirality == Chirality.Left ? XRNode.LeftHand : XRNode.RightHand; }
}
#else
public VRNode xrNode
{
get { return chirality == Chirality.Left ? VRNode.LeftHand : VRNode.RightHand; }
}
#endif
///
/// Gets whether the controller is a left-hand controller.
///
public override bool isLeft
{
get { return chirality == Chirality.Left; }
}
///
/// Gets the last-tracked position of the controller.
///
public override Vector3 position
{
get
{
return this.transform.position;
}
}
///
/// Gets the last-tracked rotation of the controller.
///
public override Quaternion rotation
{
get
{
return this.transform.rotation;
}
}
///
/// Gets the current velocity of the controller.
///
public override Vector3 velocity
{
get
{
if (_hasTrackedPositionLastFrame)
{
return (this.transform.position - _trackedPositionLastFrame) / Time.fixedDeltaTime;
}
else
{
return Vector3.zero;
}
}
}
///
/// Gets the type of controller this is. For InteractionVRController, the type is
/// always ControllerType.VRController.
///
public override ControllerType controllerType
{
get { return ControllerType.XRController; }
}
///
/// This implementation of InteractionControllerBase does not represent a Leap hand,
/// so it need not return an InteractionHand object.
///
public override InteractionHand intHand
{
get { return null; }
}
///
/// InteractionVRController doesn't need to do anything when an object is
/// unregistered.
///
protected override void onObjectUnregistered(IInteractionBehaviour intObj) { }
#endregion
#region Hover Implementation
///
/// Gets the center point used for hover distance checking.
///
public override Vector3 hoverPoint
{
get { return _hoverPoint == null ? Vector3.zero : _hoverPoint.position; }
}
///
/// Gets the list of points to be used to perform higher-fidelity "primary hover"
/// checks. Only one interaction object may be the primary hover of an interaction
/// controller (Leap hand or otherwise) at a time. Interface objects such as buttons
/// can only be pressed when they are primarily hovered by an interaction controller,
/// so it's best to return points on whatever you expect to be able to use to push
/// buttons with the controller.
///
protected override List _primaryHoverPoints
{
get { return primaryHoverPoints; }
}
private Vector3 _pivotingPositionOffset = Vector3.zero;
private Vector3 _unwarpingPositionOffset = Vector3.zero;
private Quaternion _unwarpingRotationOffset = Quaternion.identity;
protected override void unwarpColliders(Transform primaryHoverPoint, ISpaceComponent warpedSpaceElement)
{
// Extension method calculates "unwarped" pose in world space.
Vector3 unwarpedPosition;
Quaternion unwarpedRotation;
warpedSpaceElement.anchor.transformer.WorldSpaceUnwarp(primaryHoverPoint.position,
primaryHoverPoint.rotation,
out unwarpedPosition,
out unwarpedRotation);
// Shift the controller to have its origin on the primary hover point so that
// rotations applied to the hand cause it to pivot around that point, then apply
// the position and rotation transformation.
_pivotingPositionOffset = -primaryHoverPoint.position;
_unwarpingPositionOffset = unwarpedPosition;
_unwarpingRotationOffset = unwarpedRotation * Quaternion.Inverse(primaryHoverPoint.rotation);
refreshContactBoneTargets(useUnwarpingData: true);
}
#endregion
#region Contact Implementation
private Vector3[] _contactBoneLocalPositions;
private Quaternion[] _contactBoneLocalRotations;
private Vector3[] _contactBoneTargetPositions;
private Quaternion[] _contactBoneTargetRotations;
private ContactBone[] _contactBones;
public override ContactBone[] contactBones
{
get { return _contactBones; }
}
private GameObject _contactBoneParent;
protected override GameObject contactBoneParent
{
get { return _contactBoneParent; }
}
protected override bool initContact()
{
initContactBones();
if (_contactBoneParent == null)
{
_contactBoneParent = new GameObject("VR Controller Contact Bones "
+ (isLeft ? "(Left)" : "(Right"));
}
foreach (var contactBone in _contactBones)
{
contactBone.transform.parent = _contactBoneParent.transform;
}
return true;
}
private void refreshContactBoneTargets(bool useUnwarpingData = false)
{
if (_wasContactInitialized)
{
// Move the controller transform temporarily into its "unwarped space" pose
// (only if we are using the controller in a curved space)
if (useUnwarpingData)
{
moveControllerTransform(_pivotingPositionOffset, Quaternion.identity);
moveControllerTransform(_unwarpingPositionOffset, _unwarpingRotationOffset);
}
for (int i = 0; i < _contactBones.Length; i++)
{
_contactBoneTargetPositions[i]
= this.transform.TransformPoint(_contactBoneLocalPositions[i]);
_contactBoneTargetRotations[i]
= this.transform.TransformRotation(_contactBoneLocalRotations[i]);
}
// Move the controller transform back to its original pose.
if (useUnwarpingData)
{
moveControllerTransform(-_unwarpingPositionOffset, Quaternion.Inverse(_unwarpingRotationOffset));
moveControllerTransform(-_pivotingPositionOffset, Quaternion.identity);
}
}
}
private void moveControllerTransform(Vector3 deltaPosition, Quaternion deltaRotation)
{
this.transform.rotation = deltaRotation * this.transform.rotation;
this.transform.position = deltaPosition + this.transform.position;
}
private List _contactBoneBuffer = new List();
private List _colliderBuffer = new List();
private void initContactBones()
{
_colliderBuffer.Clear();
_contactBoneBuffer.Clear();
// Scan for existing colliders and construct contact bones out of them.
Utils.FindColliders(this.gameObject, _colliderBuffer,
includeInactiveObjects: true);
foreach (var collider in _colliderBuffer)
{
if (collider.isTrigger) continue; // Contact Bones are for "contacting" colliders.
ContactBone contactBone = collider.gameObject.AddComponent();
Rigidbody body = collider.gameObject.GetComponent();
if (body == null)
{
body = collider.gameObject.AddComponent();
}
body.freezeRotation = true;
body.useGravity = false;
body.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
body.mass = 1F;
contactBone.interactionController = this;
contactBone.rigidbody = body;
contactBone.collider = collider;
_contactBoneBuffer.Add(contactBone);
}
//The number of bones is equal to the colliders found that are not set to trigger
int numBones = _contactBoneBuffer.Count;
_contactBones = new ContactBone[numBones];
_contactBoneLocalPositions = new Vector3[numBones];
_contactBoneLocalRotations = new Quaternion[numBones];
_contactBoneTargetPositions = new Vector3[numBones];
_contactBoneTargetRotations = new Quaternion[numBones];
for (int i = 0; i < numBones; i++)
{
_contactBones[i] = _contactBoneBuffer[i];
_contactBoneLocalPositions[i]
= _contactBoneTargetPositions[i]
= this.transform.InverseTransformPoint(_contactBones[i].transform.position);
_contactBoneLocalRotations[i]
= _contactBoneTargetRotations[i]
= this.transform.InverseTransformRotation(_contactBones[i].transform.rotation);
}
}
protected override void getColliderBoneTargetPositionRotation(int contactBoneIndex,
out Vector3 targetPosition,
out Quaternion targetRotation)
{
targetPosition = _contactBoneTargetPositions[contactBoneIndex];
targetRotation = _contactBoneTargetRotations[contactBoneIndex];
}
#endregion
#region Grasping Implementation
///
/// By default, InteractionVRController uses Input.GetAxis(graspButtonAxis) to
/// determine the "depression" state for the grasp button. By setting this value to
/// something other than null, it is possible to modify this behavior to instead
/// retrieve a grasping axis value based on arbitrary code.
///
/// A grasp is attempted when the grasp button axis value returned by this method
/// becomes larger than the graspButtonDepressedValue, and a grasp is released when
/// the grasp button axis value returned by this method becomes smaller than the
/// graspButtonReleasedValue. Both of these values provide public setters.
///
public Func graspAxisOverride = null;
private float _graspDepressedValue = 0.8F;
///
/// The value between 0 and 1 past which the grasping axis value will cause an
/// attempt to grasp a graspable interaction object near the grasp point.
///
public float graspDepressedValue
{
get { return _graspDepressedValue; }
set { _graspDepressedValue = value; }
}
private float _graspReleasedValue = 0.7F;
///
/// If the grasping axis value passes the graspDepressedValue, it must then drop
/// underneath this value in order to release the grasp attempt (potentially
/// releasing a held object) and allow a new grasp attempt to occur.
///
public float graspReleasedValue
{
get { return _graspReleasedValue; }
set { _graspReleasedValue = value; }
}
private List _graspManipulatorPointsBuffer = new List();
///
/// Gets a list returning this controller's hoverPoint. Because the
/// InteractionVRController represents a rigid controller, any two points that
/// rigidly move with the controller position and orientation will provide enough
/// information.
///
public override List graspManipulatorPoints
{
get
{
_graspManipulatorPointsBuffer.Clear();
_graspManipulatorPointsBuffer.Add(hoverPoint);
_graspManipulatorPointsBuffer.Add(hoverPoint + this.transform.rotation * Vector3.forward * 0.05F);
_graspManipulatorPointsBuffer.Add(hoverPoint + this.transform.rotation * Vector3.right * 0.05F);
return _graspManipulatorPointsBuffer;
}
}
private IInteractionBehaviour _closestGraspableObject = null;
private bool _graspButtonLastFrame = false;
private bool _graspButtonDown = false;
private bool _graspButtonUp = false;
private float _graspButtonDownSlopTimer = 0F;
private bool _inputWarningDisplayed = false;
public override Vector3 GetGraspPoint()
{
return graspPoint.transform.position;
}
protected override void fixedUpdateGraspingState()
{
refreshClosestGraspableObject();
fixedUpdateGraspButtonState();
}
private void refreshClosestGraspableObject()
{
_closestGraspableObject = null;
float closestGraspableDistance = float.PositiveInfinity;
foreach (var intObj in graspCandidates)
{
float testDist = intObj.GetHoverDistance(this.graspPoint.position);
if (testDist < maxGraspDistance && testDist < closestGraspableDistance)
{
_closestGraspableObject = intObj;
closestGraspableDistance = testDist;
}
}
}
private void fixedUpdateGraspButtonState(bool ignoreTemporal = false)
{
_graspButtonDown = false;
_graspButtonUp = false;
bool graspButton = _graspButtonLastFrame;
if (!_graspButtonLastFrame)
{
if (graspAxisOverride == null)
{
try
{
graspButton = Input.GetAxis(graspButtonAxis) > graspDepressedValue;
}
catch
{
if (!_inputWarningDisplayed)
{
Debug.LogWarning("VR CONTROLLER INPUT AXES ARE NOT SET UP. Go to your Input Manager " +
"and add a definition for " + graspButtonAxis + " on the " + (isLeft ? "9" : "10") + "th " +
"Joystick Axis or disable this controller.", this);
_inputWarningDisplayed = true;
}
graspButton = Input.GetKey(isLeft ? KeyCode.JoystickButton14 : KeyCode.JoystickButton15);
}
}
else
{
graspButton = graspAxisOverride() > graspDepressedValue;
}
if (graspButton)
{
// Grasp button was _just_ depressed this frame.
_graspButtonDown = true;
_graspButtonDownSlopTimer = graspTimingSlop;
}
}
else
{
if (graspReleasedValue > graspDepressedValue)
{
Debug.LogWarning("The graspReleasedValue should be less than or equal to the "
+ "graspDepressedValue!", this);
graspReleasedValue = graspDepressedValue;
}
if (graspAxisOverride == null)
{
try
{
graspButton = Input.GetAxis(graspButtonAxis) > graspDepressedValue;
}
catch
{
if (!_inputWarningDisplayed)
{
Debug.LogWarning("VR CONTROLLER INPUT AXES ARE NOT SET UP. Go to your Input Manager " +
"and add a definition for " + graspButtonAxis + " on the " + (isLeft ? "9" : "10") + "th " +
"Joystick Axis or disable this controller.", this);
_inputWarningDisplayed = true;
}
graspButton = Input.GetKey(isLeft ? KeyCode.JoystickButton14 : KeyCode.JoystickButton15);
}
}
else
{
graspButton = graspAxisOverride() > graspReleasedValue;
}
if (!graspButton)
{
// Grasp button was _just_ released this frame.
_graspButtonUp = true;
_graspButtonDownSlopTimer = 0F;
}
}
if (_graspButtonDownSlopTimer > 0F)
{
_graspButtonDownSlopTimer -= Time.fixedDeltaTime;
}
_graspButtonLastFrame = graspButton;
}
protected override bool checkShouldGrasp(out IInteractionBehaviour objectToGrasp)
{
bool shouldGrasp = !isGraspingObject
&& (_graspButtonDown || _graspButtonDownSlopTimer > 0F)
&& _closestGraspableObject != null;
objectToGrasp = null;
if (shouldGrasp) { objectToGrasp = _closestGraspableObject; }
return shouldGrasp;
}
///
/// If the provided object is within range of this VR controller's grasp point and
/// the grasp button is currently held down, this method will manually initiate a
/// grasp and return true. Otherwise, the method returns false.
///
protected override bool checkShouldGraspAtemporal(IInteractionBehaviour intObj)
{
bool shouldGrasp = !isGraspingObject
&& _graspButtonLastFrame
&& intObj.GetHoverDistance(graspPoint.position) < maxGraspDistance;
if (shouldGrasp)
{
var tempControllers = Pool>.Spawn();
try
{
intObj.BeginGrasp(tempControllers);
}
finally
{
tempControllers.Clear();
Pool>.Recycle(tempControllers);
}
}
return shouldGrasp;
}
protected override bool checkShouldRelease(out IInteractionBehaviour objectToRelease)
{
bool shouldRelease = _graspButtonUp && isGraspingObject;
objectToRelease = null;
if (shouldRelease) { objectToRelease = graspedObject; }
return shouldRelease;
}
#endregion
#region Gizmos
public override void OnDrawRuntimeGizmos(RuntimeGizmos.RuntimeGizmoDrawer drawer)
{
base.OnDrawRuntimeGizmos(drawer);
// Grasp Point
float graspAmount = 0F;
if (graspAxisOverride != null) graspAmount = graspAxisOverride();
else
{
try
{
graspAmount = Input.GetAxis(graspButtonAxis);
}
catch (ArgumentException) { }
}
drawer.color = Color.Lerp(GizmoColors.GraspPoint, Color.white, graspAmount);
drawer.DrawWireSphere(GetGraspPoint(), maxGraspDistance);
// Nearest graspable object
if (_closestGraspableObject != null)
{
drawer.color = Color.Lerp(GizmoColors.Graspable, Color.white, Mathf.Sin(Time.time * 2 * Mathf.PI * 2F));
drawer.DrawWireSphere(_closestGraspableObject.rigidbody.position, maxGraspDistance * 0.75F);
}
}
#endregion
}
}